<?php
/**
* @file     Yz/SQL/Adapter/Mysqli.php
* @version  0.1
*/

class Yz_SQL_Adapter_Mysqli
    extends Yz_Class
    implements Yz_SQL_Adapter_Interface,Countable
{
    // quote sign for identifiers, as per ANSI mode
    const QUOTE_IDENT = '"';

    const ER_DUP_ENTRY = 1062;
    const ER_PARSE_ERROR = 1064;
    const ER_SYNTAX_ERROR = 1149;
    const ER_NO_SUCH_TABLE = 1146;

    private $handle;
    private $qcount = 0;

    protected $result;

    protected $affectedRows;

    /**
     * Constructor
     *
     * @param string dsn
     * @param string? charset
     * @throws Yz_SQL_Adapter_Error_ConnectError
     *
     */
    public function __construct( $dsn, $dbcharset='utf8' )
    {
        $data = parse_url( $dsn );
        $dbname = trim( $data['path'], '/' );
        $dbuser = $data['user'];
        $dbpass = $data['pass'];
        $dbhost = $data['host'];

        ! empty($data['port']) and $dbhost .= ':' . $data['port'];


        $db = mysqli_init();
        if ( ! $db ) {
            throw Yz_SQL_Adapter_Error_ConnectError::_new_(array(
                'dbname' => $dbname,
                'host' =>   $dbhost,
                'message' =>  $db->connect_error,
            ));
        }
        $db->real_connect($dbhost, $dbuser, $dbpass, $dbname, null, null, MYSQLI_CLIENT_FOUND_ROWS);
        if ( $db->connect_error ) {
            throw Yz_SQL_Adapter_Error_ConnectError::_new_(array(
                'dbname' => $dbname,
                'host' =>   $dbhost,
                'message' =>  $db->connect_error,
            ));
        } // if


        if ( $dbcharset && ! $db->set_charset($dbcharset) ) {
            throw Yz_SQL_Adapter_Error_ConnectError::_new_(array(
                'dbname' => $dbname,
                'host' =>   $dbhost,
                'message' =>  $db->error,
            ));
        } // if

        // set the sql mode so that || means CONCAT rather than OR,
        // and `"` used to quote identifiers
        $db->query("SET SESSION sql_mode=ANSI");

        $this->handle = $db; // this $db is an object
        debug( 'MySQLi: Connected to Database.');
        return 1;
    } // init()



    /** query() - execute a query and return number of affected rows
     *
     *  @param string $query
     *  @throws Yz_SQL_Error_QueryError
     *  @throws Yz_SQL_Error_UniqueError
     *  @return int affected_rows
     */
    public function query( $query )
    {
        $r = $this->_execQuery( $query );

        return $r;
    } // query()



    /** execute() - execute a query
     *
     *  @param string $query
     *  @throws Yz_SQL_Error_QueryError
     *  @throws Yz_SQL_Error_UniqueError
     *  @return $this
     */
    public function execute( $query )
    {
        debug();

        $r = $this->_execQuery( $query, $saveResult=true );

        return $this;
    }



    /** quote() - does string escaping and wraps it with single quotes
     *
     * @param string var
     * @return string
     */
    public function quote( $var )
    {
        assert( 'is_scalar($var)' );
        return "'" . $this->handle->real_escape_string($var) . "'";
    }


    /** quote() - does identifier quoting : wraps it with double quotes
     *
     * @param string var
     * @return string
     */
    public function quoteIdentifier( $var )
    {
        assert( 'is_scalar($var)' );
        $q = self::QUOTE_IDENT;
        return $q . $var . $q;
    }



    /** getInsertId()
     *
     */
    public function getInsertId()
    {
        return $this->handle->insert_id;
    }



    /** getAffectedRows()
     *
     *  @return int|null
     */
    public function getAffectedRows()
    {
        return $this->affectedRows;
    }



    /** count()
     *
     *  @return int|null
     */
    public function count()
    {
        return $this->result ? $this->result->num_rows : $this->affectedRows;
    } // getInsertId()



    /** fetch() : get single row as a Yz_Hash
    *
    * @return Yz_Hash|null
    */
    public function fetch()
    {
        debug();

        $value = null;
        $r = $this->result;
        if ( $r ) {
            $row = $r->fetch_assoc();
            if ( $row )
                $value = new Yz_Hash( $row );
            else
                $this->freeResult();
        }
        return $value;
    } // fetch



    /** fetchScalar() : get a value from a single row as a scalar
    *
    * @return <scalar>|null
    */
    public function fetchScalar()
    {
        debug();

        $value = null;
        $r = $this->result;
        if ( $r ) {
            $row = $r->fetch_row();
            if( $row )
                $value = $row[0];
            else
                $this->freeResult();
        }
        return $value;
    } // fetch



    /** fetchAll() : get the whole result as a Yz_Array of Yz_Hash
     *
     * @return Yz_Array{Yz_Hash}|null
     */
    public function fetchAll()
    {
        $value = null;
        $r = $this->result;
        if ( $r ) {
            if ( $r->num_rows > 0 ) {
                $value = array();

                while ( $row = $r->fetch_assoc() )
                    $value[] = new Yz_Hash($row);
                $value = new Yz_Array($value);
            }

            $this->freeResult();
        }
        return $value;
    } // fetchAll



    /**
    *
    *
    */
    protected function _execQuery( $query, $saveResult=false )
    {
        debug();

        if ( $this->result )
            $this->freeResult();

        $this->affectedRows = null;
        $h = $this->handle;

        $this->qcount ++;
        $r = $h->query( $query );
        debug( "QUERY #{$this->qcount}:\n{$query}\n{$h->info}" );

        if ( ! $r ) {
            $errno = $h->errno;
            switch ($errno) {
                case self::ER_DUP_ENTRY:
                    throw Yz_SQL_Error_UniqueError;
                case self::ER_PARSE_ERROR:
                case self::ER_SYNTAX_ERROR:
                    throw Yz_SQL_Error_SyntaxError::_new_(array('message' => $h->error));
                case self::ER_NO_SUCH_TABLE:
                default:

            }
            throw Yz_SQL_Error_QueryError::_new_(array(
                'detail' => $h->error,
                'errno' => $errno,
            ));
        } // if

        // don't save result if no rows
        if( $saveResult && is_object($r) && $r->num_rows > 0 )
            $this->result = $r;

        $this->affectedRows = $h->affected_rows;

        return $this->affectedRows;
    } // _execQuery


    /** freeResult()
    *
    *
    */
    protected function freeResult()
    {
        assert( '$this->result != null' );
        $this->result->free();
        $this->result = null;
        return $this;
    } // freeResult


} // class


